//
//  OosObject.m
//  Oos4ios
//
//  Created by Pablo Gonzalez on 3/17/11.
//  Copyright 2011 11870.com. All rights reserved.
//

#import <CoreData/CoreData.h>
#import <oos4ios/Model/OosObject.h>
#import <oos4ios/Exceptions/MethodNotImplementedException.h>
#import "OosObjectCreator.h"
#import <objc/runtime.h>

@implementation OosObject

static NSMutableDictionary *descriptions;

@synthesize processing = mProcessing;

+ (NSString *) className
{
	return NSStringFromClass(self);
}

+ (NSEntityDescription *) entityDescription
{
	if (!descriptions) {
		descriptions = [[NSMutableDictionary alloc] init];
	}
	NSEntityDescription *description;
	description = [descriptions objectForKey: [self className]];
	if (!description) {
		description = [OosObjectCreator descriptionForClass: self];
		[descriptions setObject:description forKey:[self className]];
	}
	return description;
}

+ (id) create
{
	return [OosObjectCreator createObjectFromDescription:[self entityDescription]];
}

+ (id) getInstance
{
    
    id instance = [self objectWithPredicate:nil];
    if (!instance) {
        instance = [self create];
    }
    return instance;
//    NSArray *set = [self objectsWithPredicate:nil];
//    return [self createFromSet:nil];
}

+ (id) createFromSet: (NSArray *) set
{
	if (set && [set count] > 0) {
		return [set objectAtIndex:0];
	} else {
		return [self create];
	}
}

+ (NSArray *) executeRequest: (NSFetchRequest *) request
{
    Oos4ios *oos4ios = [Oos4ios instance];
    NSManagedObjectContext *context = [oos4ios managedContext];
    [[context persistentStoreCoordinator] lock];
    
    NSError *error = nil;
    id results = nil;
    NSUInteger trialCountdown = 8;
    // This while is used as a damage control facility. In some circunstances 
    // where CoreData is saving data into the object that we are quering now 
    // there would be an exception because the NSSet changes while processing. 
    // Since this is an internal problem with CoreData we try up to 8 times to 
    // fetch the details. The counter of 8 was set to avoid running into an infinite loop.
    while (results == nil && error == nil && trialCountdown > 0) {
        @try {
            results = [context executeFetchRequest:request error:&error];
        }
        @catch (NSException *exception) {
            NSLog(@"Exception while fetching request: %d times left", trialCountdown);
            trialCountdown--;
        }
    }
    if (error != nil)
    {
        [NSException raise:NSGenericException 
					format:@"error: %@", [error description]];
    }
    if (![results isKindOfClass:[NSArray class]]) {
        results = [NSArray arrayWithObject:results];
    }
    
    [[context persistentStoreCoordinator] unlock];
    return results;
}

+ (NSFetchRequest *) requestFromPredicate: (NSString *) format 
                           withParameters: (va_list) parameters
{
    NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
    if (format)
    {
        NSPredicate *predicate;
        predicate = [NSPredicate predicateWithFormat: format
                                           arguments: parameters];
        [request setPredicate:predicate];
    }
    return request;
}

+ (NSArray *)objectsfromEntity: (NSEntityDescription *) entity 
                 withPredicate: (NSString *)format
                withParameters: (va_list) parameters
{
    NSFetchRequest *request = [self  requestFromPredicate:format 
                                           withParameters:parameters];
    
    [request setEntity:entity];
	
    return [self executeRequest:request];
}

+ (id)objectfromEntity: (NSEntityDescription *) entity 
         withPredicate: (NSString *)format
        withParameters: (va_list) parameters
{
    NSFetchRequest *request = [self  requestFromPredicate:format 
                                           withParameters:parameters];
    [request setEntity:entity];
    [request setFetchLimit: 1];
    
    id result = [self executeRequest:request];
	
    if ([result isKindOfClass:[NSArray class]]) {
        if ([(NSArray *)result count] > 0) {
            result = [result objectAtIndex:0];
        } else {
            result = nil;
        }
    }
    return result;
}


+ (NSArray *)objectsWithPredicate:(id)stringOrPredicate, ...
{
    
    NSEntityDescription *entity = [self entityDescription];
    NSArray *result = nil;
    va_list variadicArguments;
    va_start(variadicArguments, stringOrPredicate);
    result = [self objectsfromEntity: (NSEntityDescription *) entity 
                        withPredicate: (NSString *)stringOrPredicate
                       withParameters: (va_list) variadicArguments];
    va_end(variadicArguments);
    
    return result;
}

+ (NSArray *)objectsfromEntity: (NSEntityDescription *) entity 
                 withPredicate: (id)stringOrPredicate, ... 
{
    NSArray *result = nil;
    va_list variadicArguments;
    va_start(variadicArguments, stringOrPredicate);
    result = [self objectsfromEntity: (NSEntityDescription *) entity 
                       withPredicate: (NSString *)stringOrPredicate
                      withParameters: (va_list) variadicArguments];
    va_end(variadicArguments);
    
    return result;
}

+ (id)objectWithPredicate: (id)stringOrPredicate, ... 
{
    NSEntityDescription *entity = [self entityDescription];
    id result = nil;
    va_list variadicArguments;
    va_start(variadicArguments, stringOrPredicate);
    result = [self objectfromEntity: (NSEntityDescription *) entity 
                      withPredicate: (NSString *)stringOrPredicate
                     withParameters: (va_list) variadicArguments];
    va_end(variadicArguments);
    
    return result;
}

+ (id)objectfromEntity: (NSEntityDescription *) entity 
         withPredicate: (id)stringOrPredicate, ...
{
    id result = nil;
    va_list variadicArguments;
    va_start(variadicArguments, stringOrPredicate);
    result = [self objectfromEntity: (NSEntityDescription *) entity 
                      withPredicate: (NSString *)stringOrPredicate
                     withParameters: (va_list) variadicArguments];
    va_end(variadicArguments);
    
    return result; 
}


#ifdef DEBUG

static int tabCount = 0;

- (NSString *) tabs
{
    NSMutableString *tabs = [NSMutableString string];
    for (int i = tabCount; i > 0; i--) {
        [tabs appendString:@"  "];
    }
    return tabs;
}

static NSMutableArray *processed;

- (NSMutableArray *) processed
{
    if (!processed) {
        processed = [[NSMutableArray array] retain];
    }
    return processed;
}

- (NSString *)description
{
    NSString *name = NSStringFromClass([self class]);
    if ([[self processed] containsObject:name] || tabCount > 4) {
        return @"(...)";
    }
    [[self processed] addObject:[self class]];
    NSMutableArray *subObjects = [NSMutableArray array];
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    
    tabCount++;
    for(i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propName = property_getName(property);
        if(propName) {
            NSString *propertyName = [NSString stringWithFormat:@"%s", propName];
            id object = [self valueForKey:propertyName];
            if (object) {
                NSString *value;
                if ([object isKindOfClass:[NSSet class]]) {
                    NSSet *set = (NSSet *) object;
                    if ([set count]) {
                        NSString *plasted = [[set allObjects] componentsJoinedByString:@",\r"];
                        value = [NSString stringWithFormat: @"[%@]", plasted];
                    } else {
                        value = @"[]";
                    }
                } else {
                    value = [object description];
                }
                [subObjects addObject:[NSString stringWithFormat:@"%@%@ : %@", 
                                       [self tabs], propertyName, value, nil]];
            }
        }
    }
    free(properties);
    
    tabCount--;
    NSString *result;
    if (outCount) {
        NSString *subString = [subObjects componentsJoinedByString:@",\r"];
        result = [NSString stringWithFormat:@"(%@){\r%@\r%@}", 
                  name, subString, [self tabs], nil];
    } else {
        //result = @" : {}";
        result = [NSString stringWithFormat:@"(%@){}", 
                  name, nil];
    }
    [[self processed] removeObject:name];
    return result;
}
#endif

@end
